package network2;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
/*packet就相当于是一个数据包，然后在socket文件上来进行发送，socket文件是用来传输数据的*/
//UDP版本的回显服务器
public class UdpEchoServer {
    /*网络编程，本质上是要操作网卡，但是网卡不方便直接操作，在操作系统内核中
    * 使用了一种特殊的叫做”socket“这样的文件来抽象表示网卡，因此进行网络通信，必须
    * 先有一个socket对象*/
    private DatagramSocket socket = null;
    //对于服务器来说创建socket对象的同时，要让他绑定上一个具体的端口号
    //服务器一定要关联上一个具体的端口号的！！
    //服务器是网络传输中，被动的一方，如果操作系统是随机分配的端口，此时客户端就不知道这个端口是啥了，
    //也就无法进行通信了
    /*也就是说服务器在开始构造的时候一定先要有一个socket文件，否则就无法进行网络间的通信，但是光有这个socket
    * 文件还不够，此时需要把这个socket文件绑定上一个端口号，此时客户端才能知道服务器在网络中的具体位置*/
    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动：");
        while (true) {
            //服务器不是给一个客户端提供服务就完了，需要给很多个客户端来提供服务
            //1.读取客户端发来的请求是啥，
            //receive方法的参数是一个输出型的参数，需要先构造好一个DatagramPacket对象，然后交给receive方法来进行填充
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(requestPacket);//receive方法就是把这个空的字节数组把请求的数据填充进来
            //此时的这个DatagramPacket 是一个特殊的对象，并不方便直接 处理，可以把这里包含的数据拿出来，构造成一个字符串
            String request = new String(requestPacket.getData(),0, requestPacket.getLength());
            /*字符数组作为存储数据的缓冲区，此时可能不一定会用到这么大的数组，只是用了一小部分，所以构造字符串就构造那部分用了的
             * 数组即可，而getLength就是获取有效长度*/
            //根据请求计算响应，此处由于是回显服务器，请求和响应就是相同的
            String response = process(request);
            //3.把响应写回到客户端，send的参数也是DatagramPacket，需要把这个packet对象构造好，
            //此处构造响应的对象不能是空的字节数组来构造了，应该用计算好的响应来构造数据
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
                    response.getBytes().length, requestPacket.getSocketAddress());
            //此时不能只是构造一个单纯的响应数据了，需要把这个响应发送给哪个客户端这个参数也要写好
            /*注意，上行代码有坑：第二个参数一定要写对，  response.getBytes().length是字节的长度，如果是response.length，这个
             * 就是字符的长度，但是DatagramPacket只认字节，不认字符，如果是单纯的ASCII码的英文字符此时都是一个字节，但是如果是响应的是
             * 中文字符，一个字符有好几个字节，此时长度就出问题了*/
            socket.send(responsePacket);
            //4.打印一下，当前这次请求响应的处理的中间结果
            System.out.printf("[%s: %d] req: %s; resp: %s\n",requestPacket.getAddress().toString(),
                    requestPacket.getPort(),request, response);
        }
    }

    //这个方法就是根据请求来计算响应
    private String process(String request) {
        return request;
    }
    public static void main(String[] args) throws IOException {
        //创建服务器对象并且启动服务器
        //端口号的指定可以是随便指定的，只要是从1024 ——> 65535之间挑选一个即可
        UdpEchoServer server2 = new UdpEchoServer(9090);
        server2.start();
    }
}
/*主要立即服务器的工作流程 ：1.接收客户端发送过来的请求并解析，   2.根据请求来构造响应，
* 3.构造响应并把响应写回给客户端*/
/*receive()方法：当服务器开始启动的时候，此时因为需要处理多个客户端的请求，此时需要写一个while循环
* 但是这个while循环不会一直告诉旋转，当有客户端发来请求的时候，此时while循环就开始运转，当没有客户端
* 发送给服务器请求的时候，此时是在receive()方法这里阻塞着的，不会一直进行*/
/*当同一时刻客户端发送过来多个请求，此时这一个while循环可能处理不了这么多的请求，此时引入高并发这个概念，
* 此时就体现了多线程的好处，但是多线程归多线程，一台计算机的硬件资源终究是有限的，但是客户端的请求可能是
* 一台计算机处理不了的，此时就有了”分布式的概念“，也就是多台机器来一起充当服务器，此时高并发的请求就可以同一时间
* 来处理了，但是分布式，引入了高成本的管理，需要让每一个机器都听话的工作，分工明确，相互配合好。*/

